﻿using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using DeepEarth.Client.MapControl;
using DeepEarth.Client.MapControl.Events;

namespace DeepEarth.Client.Controls.DEMap.ZoomSlider
{
    [TemplatePart(Name = PART_ZoomSlider, Type = typeof(Slider))]
    public class ZoomSlider : Slider, IMapControl<Map>
    {
        private const string PART_ZoomSlider = "PART_ZoomSlider";

        private bool _hasFocus;
        private bool _isZooming;
        private Map _map;
        private Slider _slider;

        private bool _updateSlider;

        public ZoomSlider()
        {
            DefaultStyleKey = typeof(ZoomSlider);

            if(!DesignerProperties.IsInDesignTool)
            {

            }
        }

        public string Id { get; set; }

        public bool IsVisible { get; set; }
        public bool SnapToZoomLevel { get; set; }

        #region IMapControl<Map> Members

        public virtual Map MapInstance
        {
            get { return _map ?? (_map = Map.GetMapInstance(this)); }
            set
            {
                if(ReferenceEquals(_map, value))
                {
                    return;
                }
                _map = value;
            }
        }

        public string MapName { get; set; }

        public void Dispose()
        {
            MapInstance = null;
        }

        #endregion

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            if(!DesignerProperties.IsInDesignTool)
            {
                _map = MapInstance;
                _slider = (Slider)GetTemplateChild("PART_ZoomSlider");

                if(!DesignerProperties.IsInDesignTool)
                {
                    // Slider Events
                    _slider.Loaded += (o, e) => SetTileSourceZoomLevels();
                    _slider.GotFocus += Slider_GotFocus;
                    _slider.LostFocus += Slider_LostFocus;
                    _slider.ValueChanged += ZoomControl_ValueChanged;

                    // Map Events
                    _map.Events.MapMouseEnter += Events_MapMouseEnter;
                    _map.Events.MapMouseLeave += Events_MapMouseLeave;
                    _map.Events.MapZoomStarted += Events_MapZoomStarted;
                    _map.Events.MapZoomEnded += Events_MapZoomEnded;
                    _map.Events.MapZoomChanged += Events_MapZoomChanged;
                    _map.Events.MapMouseWheel += Events_MapMouseWheel;
                    _map.Events.MapDoubleClick += Events_MapDoubleClick;
                    _map.Events.MapTileSourceChanged += Events_MapTileSourceChanged;

                    _updateSlider = true; 
                }
            }
        }

        private void SetTileSourceZoomLevels()
        {
            this._slider.Maximum = MapInstance.BaseLayer.Source.MaxZoomLevel;
            this._slider.Minimum = MapInstance.BaseLayer.Source.MinZoomLevel;
        }

        private void Slider_LostFocus(object sender, RoutedEventArgs e)
        {
            _hasFocus = false;
            if(_isZooming)
            {
                _updateSlider = false;
            }
        }

        private void Slider_GotFocus(object sender, RoutedEventArgs e)
        {
            _hasFocus = true;
        }

        private void Events_MapMouseEnter(object sender, MouseEventArgs args)
        {
            VisualStateManager.GoToState(this, "MouseOver", true);
        }

        private void Events_MapMouseLeave(object sender, MouseEventArgs args)
        {
            VisualStateManager.GoToState(this, "Normal", true);
        }

        private void Events_MapMouseWheel(object sender, MapMouseWheelEventArgs mapMouseWheelEventArgs)
        {
            _updateSlider = true;
        }

        private void Events_MapDoubleClick(object sender, MouseButtonEventArgs args)
        {
            _updateSlider = true;
        }

        private void Events_MapZoomEnded(object sender, MapEventArgs args)
        {
            _isZooming = false;
            _updateSlider = true;
        }

        private void Events_MapZoomStarted(object sender, MapEventArgs mapEventArgs)
        {
            _isZooming = true;
        }

        private void Events_MapTileSourceChanged(object sender, MapEventArgs args)
        {
            SetTileSourceZoomLevels();
        }

        private void Events_MapZoomChanged(object sender, double zoomLevel)
        {
            if((_isZooming == false) || (_hasFocus == false && _updateSlider))
            {
                _slider.Value = zoomLevel;
            }
        }

        private void ZoomControl_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if(_hasFocus)
            {
                if(SnapToZoomLevel)
                {
                    _slider.Value = Math.Round(_slider.Value);
                }

                if(_map.BaseLayer.Source.IsValidZoomLevel(_slider.Value))
                {
                    Rect bounds = _map.LogicalBounds;

                    Point pixelBoxOrigin = _map.CoordHelper.LogicalToPixel(new Point(bounds.Left, bounds.Top));
                    Point pixelBoxExtent = _map.CoordHelper.LogicalToPixel(new Point(bounds.Right, bounds.Bottom));
                    var pixelBox = new Rect(pixelBoxOrigin, pixelBoxExtent);

                    var pixelBoxCenter = new Point
                                         {
                                             X = (pixelBox.X + pixelBox.Width/2),
                                             Y = (pixelBox.Y + pixelBox.Height/2)
                                         };

                    Point logicalPoint = _map.CoordHelper.PixelToLogical(pixelBoxCenter);
                    Size viewSize = _map.CoordHelper.ZoomLevelToLogicalView(_slider.Value);

                    double factorX = pixelBoxCenter.X/_map.MapViewPixelSize.Width;
                    double factorY = pixelBoxCenter.Y/_map.MapViewPixelSize.Height;

                    var targetLogicalOrigin = new Point
                                              {
                                                  X = (logicalPoint.X - (viewSize.Width*factorX)),
                                                  Y = (logicalPoint.Y - (viewSize.Height*factorY))
                                              };

                    _map.BaseLayer.Msi.ViewportOrigin = targetLogicalOrigin;
                    _map.BaseLayer.Msi.ViewportWidth = viewSize.Width;
                }
            }
        }
    }
}